/*
 *	Xing VBR tagging for LAME.
 *
 *	Copyright (c) 1999 A.L. Faber
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

/* $Id: VbrTag.c,v 1.2 2006/02/09 16:56:23 kramm Exp $ */

#include <stdlib.h>
#include "config_static.h"

#include "machine.h"
#if defined(__riscos__) && defined(FPA10)
#include	"ymath.h"
#else 
#include	<math.h>
#endif


#include "bitstream.h"
#include "lame.h"
#include "VbrTag.h"
#include "version.h"

#include	<assert.h>
#include 	<stdlib.h>
#include 	<string.h>

#ifdef WITH_DMALLOC
#include <dmalloc.h>
#endif

#ifdef _DEBUG
/*  #define DEBUG_VBRTAG */
#endif

/*
//    4 bytes for Header Tag
//    4 bytes for Header Flags
//  100 bytes for entry (NUMTOCENTRIES)
//    4 bytes for FRAME SIZE
//    4 bytes for STREAM_SIZE
//    4 bytes for VBR SCALE. a VBR quality indicator: 0=best 100=worst
//   20 bytes for LAME tag.  for example, "LAME3.12 (beta 6)"
// ___________
//  140 bytes
*/
#define VBRHEADERSIZE (NUMTOCENTRIES+4+4+4+4+4)

#define LAMEHEADERSIZE (VBRHEADERSIZE + 9 + 1 + 1 + 8 + 1 + 1 + 3 + 1 + 1 + 2 + 4 + 2 + 2)

/* the size of the Xing header (MPEG1 and MPEG2) in kbps */
#define XING_BITRATE1 128
#define XING_BITRATE2  64
#define XING_BITRATE25 32



const static char	VBRTag[]={"Xing"};
const static char	VBRTag2[]={"Info"};




/* Lookup table for fast CRC computation
 * See 'CRC_update_lookup'
 * Uses the polynomial x^16+x^15+x^2+1 */

unsigned int crc16_lookup[256] =
{
	0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241,
	0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440,
	0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40, 
	0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841,
	0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40, 
	0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41, 
	0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641,
	0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040,
	0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240,
	0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441, 
	0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41, 
	0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840,
	0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41, 
	0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40,
	0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640, 
	0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041,
	0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240, 
	0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441, 
	0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41,
	0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840, 
	0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41,
	0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40,
	0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640, 
	0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041, 
	0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241, 
	0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440,
	0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40, 
	0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841,
	0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40,
	0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41,
	0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641,
	0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040
};





/***********************************************************************
 *  Robert Hegemann 2001-01-17
 ***********************************************************************/

static void addVbr(VBR_seek_info_t * v, int bitrate)
{
    int i;

    v->sum += bitrate;
    v->seen ++;
    
    if (v->seen < v->want) {
        return;
    }

    if (v->pos < v->size) {
        v->bag[v->pos] = v->sum;
        v->pos ++;
        v->seen = 0;
    }
    if (v->pos == v->size) {
        for (i = 1; i < v->size; i += 2) {
            v->bag[i/2] = v->bag[i]; 
        }
        v->want *= 2;
        v->pos  /= 2;
    }
}

static void Xing_seek_table(VBR_seek_info_t * v, unsigned char *t)
{
    int i, index;
    int seek_point;
    
    if (v->pos <= 0)
        return;
        
    for (i = 1; i < NUMTOCENTRIES; ++i) {
        float j = i/(float)NUMTOCENTRIES, act, sum;
        index = (int)(floor(j * v->pos));
        if (index > v->pos-1)
            index = v->pos-1;
        act = v->bag[index];
        sum = v->sum;
        seek_point = (int)(256. * act / sum);
        if (seek_point > 255)
            seek_point = 255;
        t[i] = seek_point;
    }
}

#if 0
void print_seeking(unsigned char *t)
{
    int i;
    
    printf("seeking table ");
    for (i = 0; i < NUMTOCENTRIES; ++i) {
        printf(" %d ", t[i]);
    }
    printf("\n");
}
#endif



/****************************************************************************
 * AddVbrFrame: Add VBR entry, used to fill the VBR the TOC entries
 * Paramters:
 *	nStreamPos: how many bytes did we write to the bitstream so far
 *				(in Bytes NOT Bits)
 ****************************************************************************
*/
void AddVbrFrame(lame_global_flags *gfp)
{
    lame_internal_flags *gfc = gfp->internal_flags;

    int kbps = bitrate_table[gfp->version][gfc->bitrate_index];
    
    if (gfc->VBR_seek_table.bag == NULL) {
        gfc->VBR_seek_table.sum  = 0;
        gfc->VBR_seek_table.seen = 0;
        gfc->VBR_seek_table.want = 1;
        gfc->VBR_seek_table.pos  = 0;
        gfc->VBR_seek_table.bag  = malloc (400*sizeof(int));
        if (gfc->VBR_seek_table.bag != NULL) {
            gfc->VBR_seek_table.size = 400;
        }
        else {
            gfc->VBR_seek_table.size = 0;
            ERRORF (gfc,"Error: can't allocate VbrFrames buffer\n");
            return;
        }   
    }
    addVbr(&gfc->VBR_seek_table, kbps);
    gfp->nVbrNumFrames++;
}


/*-------------------------------------------------------------*/
static int ExtractI4(unsigned char *buf)
{
	int x;
	/* big endian extract */
	x = buf[0];
	x <<= 8;
	x |= buf[1];
	x <<= 8;
	x |= buf[2];
	x <<= 8;
	x |= buf[3];
	return x;
}

static void CreateI4(unsigned char *buf, int nValue)
{
        /* big endian create */
	buf[0]=(nValue>>24)&0xff;
	buf[1]=(nValue>>16)&0xff;
	buf[2]=(nValue>> 8)&0xff;
	buf[3]=(nValue    )&0xff;
}



static void CreateI2(unsigned char *buf, int nValue)
{
        /* big endian create */
	buf[0]=(nValue>> 8)&0xff;
	buf[1]=(nValue    )&0xff;
}


/*-------------------------------------------------------------*/
/* Same as GetVbrTag below, but only checks for the Xing tag.
   requires buf to contain only 40 bytes */
/*-------------------------------------------------------------*/
int CheckVbrTag(unsigned char *buf)
{
	int			h_id, h_mode, h_sr_index;

	/* get selected MPEG header data */
	h_id       = (buf[1] >> 3) & 1;
	h_sr_index = (buf[2] >> 2) & 3;
	h_mode     = (buf[3] >> 6) & 3;

	/*  determine offset of header */
	if( h_id )
	{
                /* mpeg1 */
		if( h_mode != 3 )	buf+=(32+4);
		else				buf+=(17+4);
	}
	else
	{
                /* mpeg2 */
		if( h_mode != 3 ) buf+=(17+4);
		else              buf+=(9+4);
	}

	if( buf[0] != VBRTag[0] && buf[0] != VBRTag2[0] ) return 0;    /* fail */
	if( buf[1] != VBRTag[1] && buf[1] != VBRTag2[1]) return 0;    /* header not found*/
	if( buf[2] != VBRTag[2] && buf[2] != VBRTag2[2]) return 0;
	if( buf[3] != VBRTag[3] && buf[3] != VBRTag2[3]) return 0;
	return 1;
}

int GetVbrTag(VBRTAGDATA *pTagData,  unsigned char *buf)
{
	int			i, head_flags;
	int			h_bitrate,h_id, h_mode, h_sr_index;
        int enc_delay,enc_padding; 

	/* get Vbr header data */
	pTagData->flags = 0;

	/* get selected MPEG header data */
	h_id       = (buf[1] >> 3) & 1;
	h_sr_index = (buf[2] >> 2) & 3;
	h_mode     = (buf[3] >> 6) & 3;
        h_bitrate  = ((buf[2]>>4)&0xf);
	h_bitrate = bitrate_table[h_id][h_bitrate];

        /* check for FFE syncword */
        if ((buf[1]>>4)==0xE) 
            pTagData->samprate = samplerate_table[2][h_sr_index];
        else
            pTagData->samprate = samplerate_table[h_id][h_sr_index];
        //	if( h_id == 0 )
        //		pTagData->samprate >>= 1;



	/*  determine offset of header */
	if( h_id )
	{
                /* mpeg1 */
		if( h_mode != 3 )	buf+=(32+4);
		else				buf+=(17+4);
	}
	else
	{
                /* mpeg2 */
		if( h_mode != 3 ) buf+=(17+4);
		else              buf+=(9+4);
	}

	if( buf[0] != VBRTag[0] && buf[0] != VBRTag2[0] ) return 0;    /* fail */
	if( buf[1] != VBRTag[1] && buf[1] != VBRTag2[1]) return 0;    /* header not found*/
	if( buf[2] != VBRTag[2] && buf[2] != VBRTag2[2]) return 0;
	if( buf[3] != VBRTag[3] && buf[3] != VBRTag2[3]) return 0;

	buf+=4;

	pTagData->h_id = h_id;

	head_flags = pTagData->flags = ExtractI4(buf); buf+=4;      /* get flags */

	if( head_flags & FRAMES_FLAG )
	{
		pTagData->frames   = ExtractI4(buf); buf+=4;
	}

	if( head_flags & BYTES_FLAG )
	{
		pTagData->bytes = ExtractI4(buf); buf+=4;
	}

	if( head_flags & TOC_FLAG )
	{
		if( pTagData->toc != NULL )
		{
			for(i=0;i<NUMTOCENTRIES;i++)
				pTagData->toc[i] = buf[i];
		}
		buf+=NUMTOCENTRIES;
	}

	pTagData->vbr_scale = -1;

	if( head_flags & VBR_SCALE_FLAG )
	{
		pTagData->vbr_scale = ExtractI4(buf); buf+=4;
	}

	pTagData->headersize = 
	  ((h_id+1)*72000*h_bitrate) / pTagData->samprate;

        buf+=21;
        enc_delay = buf[0] << 4;
        enc_delay += buf[1] >> 4;
        enc_padding= (buf[1] & 0x0F)<<8;
        enc_padding += buf[2];
        // check for reasonable values (this may be an old Xing header,
        // not a INFO tag)
        if (enc_delay<0 || enc_delay > 3000) enc_delay=-1;
        if (enc_padding<0 || enc_padding > 3000) enc_padding=-1;

        pTagData->enc_delay=enc_delay;
        pTagData->enc_padding=enc_padding;

#ifdef DEBUG_VBRTAG
	fprintf(stderr,"\n\n********************* VBR TAG INFO *****************\n");
	fprintf(stderr,"tag         :%s\n",VBRTag);
	fprintf(stderr,"head_flags  :%d\n",head_flags);
	fprintf(stderr,"bytes       :%d\n",pTagData->bytes);
	fprintf(stderr,"frames      :%d\n",pTagData->frames);
	fprintf(stderr,"VBR Scale   :%d\n",pTagData->vbr_scale);
        fprintf(stderr,"enc_delay  = %i \n",enc_delay);
        fprintf(stderr,"enc_padding= %i \n",enc_padding);
	fprintf(stderr,"toc:\n");
	if( pTagData->toc != NULL )
	{
		for(i=0;i<NUMTOCENTRIES;i++)
		{
			if( (i%10) == 0 ) fprintf(stderr,"\n");
			fprintf(stderr," %3d", (int)(pTagData->toc[i]));
		}
	}
	fprintf(stderr,"\n***************** END OF VBR TAG INFO ***************\n");
#endif
	return 1;       /* success */
}


/****************************************************************************
 * InitVbrTag: Initializes the header, and write empty frame to stream
 * Paramters:
 *				fpStream: pointer to output file stream
 *				nMode	: Channel Mode: 0=STEREO 1=JS 2=DS 3=MONO
 ****************************************************************************
*/
int InitVbrTag(lame_global_flags *gfp)
{
	int nMode,SampIndex;
	lame_internal_flags *gfc = gfp->internal_flags;
#define MAXFRAMESIZE 2880 // or 0xB40, the max freeformat 640 32kHz framesize
	//	uint8_t pbtStreamBuffer[MAXFRAMESIZE];
	nMode = gfp->mode;
	SampIndex = gfc->samplerate_index;


	/* Clear Frame position array variables */
	//gfp->pVbrFrames=NULL;
	gfp->nVbrNumFrames=0;
	gfp->nVbrFrameBufferSize=0;


	/* Clear stream buffer */
	//	memset(pbtStreamBuffer,0x00,sizeof(pbtStreamBuffer));



	/*
	// Xing VBR pretends to be a 48kbs layer III frame.  (at 44.1kHz).
        // (at 48kHz they use 56kbs since 48kbs frame not big enough for
        // table of contents)
	// let's always embed Xing header inside a 64kbs layer III frame.
	// this gives us enough room for a LAME version string too.
	// size determined by sampling frequency (MPEG1)
	// 32kHz:    216 bytes@48kbs    288bytes@ 64kbs
	// 44.1kHz:  156 bytes          208bytes@64kbs     (+1 if padding = 1)
	// 48kHz:    144 bytes          192
	//
	// MPEG 2 values are the same since the framesize and samplerate
        // are each reduced by a factor of 2.
	*/
	{
	int i,bitrate,tot;
	if (1==gfp->version) {
	  bitrate = XING_BITRATE1;
	} else {
	  if (gfp->out_samplerate < 16000 )
	    bitrate = XING_BITRATE25;
	  else
	    bitrate = XING_BITRATE2;
	}

	if (gfp->VBR==vbr_off)
			bitrate = gfp->brate;
	
	gfp->TotalFrameSize= 
  	  ((gfp->version+1)*72000*bitrate) / gfp->out_samplerate;

	tot = (gfc->sideinfo_len+LAMEHEADERSIZE);

	if (gfp->TotalFrameSize < tot || 
            gfp->TotalFrameSize > MAXFRAMESIZE ) {
            // disable tag, it wont fit
            gfp->bWriteVbrTag = 0;
            return 0;
        }

	for (i=0; i<gfp->TotalFrameSize; ++i)
	  add_dummy_byte(gfp,0);
	}

	/* Success */
	return 0;
}



/* fast CRC-16 computation - uses table crc16_lookup 8*/
int CRC_update_lookup(int value, int crc)
{
	int tmp;
	tmp=crc^value;
	crc=(crc>>8)^crc16_lookup[tmp & 0xff];
	return crc;
}

void UpdateMusicCRC(uint16_t *crc,unsigned char *buffer, int size){
    int i;
    for (i=0; i<size; ++i) 
        *crc = CRC_update_lookup(buffer[i],*crc);
}




void ReportLameTagProgress(lame_global_flags *gfp,int nStart)
{
	if (!gfp->bWriteVbrTag)
		return;

	if (nStart)
		MSGF( gfp->internal_flags, "Writing LAME Tag...");
	else
		MSGF( gfp->internal_flags, "done\n");

}


/****************************************************************************
 * Jonathan Dee 2001/08/31
 *
 * PutLameVBR: Write LAME info: mini version + info on various switches used
 * Paramters:
 *				pbtStreamBuffer	: pointer to output buffer  
 *				id3v2size		: size of id3v2 tag in bytes
 *				crc				: computation of crc-16 of Lame Tag so far (starting at frame sync)
 *				
 ****************************************************************************
*/
int PutLameVBR(lame_global_flags *gfp, FILE *fpStream, uint8_t *pbtStreamBuffer, uint32_t id3v2size,  uint16_t crc)
{
    lame_internal_flags *gfc = gfp->internal_flags;
//	FLOAT fVersion = LAME_MAJOR_VERSION + 0.01 * LAME_MINOR_VERSION;

	int nBytesWritten = 0;
	int nFilesize	  = 0;		//size of fpStream. Will be equal to size after process finishes.
	int i;

    int enc_delay=lame_get_encoder_delay(gfp);       // encoder delay
    int enc_padding=lame_get_encoder_padding(gfp);   // encoder padding 

	//recall:	gfp->VBR_q is for example set by the switch -V 
	//			gfp->quality by -q, -h, -f, etc
	
	int nQuality		= (100 - 10 * gfp->VBR_q - gfp->quality);
	

	const char *szVersion	= get_lame_very_short_version();
	uint8_t nVBR;
	uint8_t nRevision = 0x00;
	uint8_t nRevMethod;
	uint8_t vbr_type_translator[] = {1,5,3,2,4,0,3};		//numbering different in vbr_mode vs. Lame tag

	uint8_t nLowpass		= ( ((gfp->lowpassfreq / 100.0)+.5) > 255 ? 255 : (gfp->lowpassfreq / 100.0)+.5 );

	ieee754_float32_t fPeakSignalAmplitude	= 0;				//TODO...
	uint16_t nRadioReplayGain		= 0;				//TODO...
	uint16_t nAudioPhileReplayGain  = 0;				//TODO...




	uint8_t nNoiseShaping			= gfp->internal_flags->noise_shaping;
	uint8_t nStereoMode				= 0;
	int		bNonOptimal				= 0;
	uint8_t nSourceFreq				= 0;
	uint8_t nMisc					= 0;
	uint32_t nMusicLength			= 0;
	int		bId3v1Present			= ((gfp->internal_flags->tag_spec.flags & CHANGED_FLAG)
		&& !(gfp->internal_flags->tag_spec.flags & V2_ONLY_FLAG));
	uint16_t nMusicCRC				= 0;

	//psy model type: Gpsycho or NsPsytune
	unsigned char    bExpNPsyTune	= gfp->exp_nspsytune & 1;
	unsigned char	 bSafeJoint		= (gfp->exp_nspsytune & 2)!=0;

	unsigned char	 bNoGapMore		= 0;
	unsigned char	 bNoGapPrevious	= 0;

	int		 nNoGapCount	= gfp->internal_flags->nogap_total;
	int		 nNoGapCurr		= gfp->internal_flags->nogap_current;


	uint8_t  nAthType		= gfp->ATHtype;	//4 bits.
	
	uint8_t  nFlags			= 0;

	// if ABR, {store bitrate <=255} else { store "-b"}
	int nABRBitrate	= (gfp->VBR==vbr_abr)?gfp->VBR_mean_bitrate_kbps:gfp->brate;

	//revision and vbr method
	if (gfp->VBR>=0 && gfp->VBR < sizeof(vbr_type_translator))
		nVBR = vbr_type_translator[gfp->VBR];
	else
		nVBR = 0x00;		//unknown.

	nRevMethod = 0x10 * nRevision + nVBR; 
	
	//nogap
	if (nNoGapCount != -1)
	{
		if (nNoGapCurr > 0)
			bNoGapPrevious = 1;

		if (nNoGapCurr < nNoGapCount-1)
			bNoGapMore = 1;
	}

	//flags

	nFlags	= nAthType	+ (bExpNPsyTune		<< 4)
						+ (bSafeJoint		<< 5)
						+ (bNoGapMore		<< 6)
						+ (bNoGapPrevious	<< 7);


	if (nQuality < 0)
		nQuality = 0;

	/*stereo mode field... a bit ugly.*/

	switch(gfp->mode)
	{
	case MONO:
		nStereoMode = 0;
		break;
	case STEREO:
		nStereoMode = 1;
		break;
	case DUAL_CHANNEL:
		nStereoMode = 2;
		break;
	case JOINT_STEREO:
		if (gfp->force_ms)
			nStereoMode = 4;
		else
			nStereoMode = 3;
		break;
	case NOT_SET:
	    /* FALLTHROUGH */
	default:
		nStereoMode = 7;
		break;
	}

	if (gfp->mode_automs)
		nStereoMode = 5;

	/*Intensity stereo : nStereoMode = 6. IS is not implemented */

	if (gfp->in_samplerate <= 32000)
		nSourceFreq = 0x00;
	else if (gfp->in_samplerate ==48000)
		nSourceFreq = 0x02;
	else if (gfp->in_samplerate > 48000)
		nSourceFreq = 0x03;
	else
		nSourceFreq = 0x01;  //default is 44100Hz.


	//Check if the user overrided the default LAME behaviour with some nasty options

	if (gfp->short_blocks == short_block_forced 			||
		gfp->short_blocks == short_block_dispensed 		||
		((gfp->lowpassfreq == -1) && (gfp->highpassfreq == -1))	|| // "-k"
		(gfp->scale_left != gfp->scale_right)			||
		gfp->disable_reservoir		||
		gfp->noATH			||
		gfp->ATHonly			||
		(nAthType == 0)    ||
		gfp->in_samplerate <= 32000)
			bNonOptimal = 1;
	
	nMisc =		nNoiseShaping
			+	(nStereoMode << 2)
			+	(bNonOptimal << 5)
			+	(nSourceFreq << 6);


	
	//get filesize
	fseek(fpStream, 0, SEEK_END);
	nFilesize = ftell(fpStream);

	
	nMusicLength = nFilesize - id3v2size;		//omit current frame
	if (bId3v1Present)
		nMusicLength-=128;                     //id3v1 present.
        nMusicCRC = gfc->nMusicCRC;


	/*Write all this information into the stream*/
	CreateI4(&pbtStreamBuffer[nBytesWritten], nQuality);
	nBytesWritten+=4;

	strncpy(&pbtStreamBuffer[nBytesWritten], szVersion, 9);
	nBytesWritten+=9;

	pbtStreamBuffer[nBytesWritten] = nRevMethod ;
	nBytesWritten++;

	pbtStreamBuffer[nBytesWritten] = nLowpass;
	nBytesWritten++;

	memmove(&pbtStreamBuffer[nBytesWritten], &fPeakSignalAmplitude, 4);
	nBytesWritten+=4;

	CreateI2(&pbtStreamBuffer[nBytesWritten],nRadioReplayGain);
	nBytesWritten+=2;

	CreateI2(&pbtStreamBuffer[nBytesWritten],nAudioPhileReplayGain);
	nBytesWritten+=2;

	pbtStreamBuffer[nBytesWritten] = nFlags;
	nBytesWritten++;

	if (nABRBitrate >= 255)
		pbtStreamBuffer[nBytesWritten] = 0xFF;
	else
		pbtStreamBuffer[nBytesWritten] = nABRBitrate;
	nBytesWritten++;

        pbtStreamBuffer[nBytesWritten   ] = enc_delay >> 4; // works for win32, does it for unix?
        pbtStreamBuffer[nBytesWritten +1] = (enc_delay << 4) + (enc_padding >> 8);
        pbtStreamBuffer[nBytesWritten +2] = enc_padding;

	nBytesWritten+=3;

	pbtStreamBuffer[nBytesWritten] = nMisc;
	nBytesWritten++;


	memset(pbtStreamBuffer+nBytesWritten,0, 3);		//unused in rev0
	nBytesWritten+=3;

	CreateI4(&pbtStreamBuffer[nBytesWritten], nMusicLength);
	nBytesWritten+=4;

	CreateI2(&pbtStreamBuffer[nBytesWritten], nMusicCRC);
	nBytesWritten+=2;

	/*Calculate tag CRC.... must be done here, since it includes
	 *previous information*/
	
	for (i = 0;i<nBytesWritten;i++)
		crc = CRC_update_lookup(pbtStreamBuffer[i], crc);
	
	CreateI2(&pbtStreamBuffer[nBytesWritten], crc);
	nBytesWritten+=2;

	return nBytesWritten;
}

/***********************************************************************
 * 
 * PutVbrTag: Write final VBR tag to the file
 * Paramters:
 *				lpszFileName: filename of MP3 bit stream
 *				nVbrScale	: encoder quality indicator (0..100)
 ****************************************************************************
*/
int PutVbrTag(lame_global_flags *gfp,FILE *fpStream,int nVbrScale)
{
    lame_internal_flags * gfc = gfp->internal_flags;

	long lFileSize;
	int nStreamIndex;
	char abyte,bbyte;
	uint8_t		btToc[NUMTOCENTRIES];
	uint8_t pbtStreamBuffer[MAXFRAMESIZE];
	
	int i;
	uint16_t crc = 0x00;
	
    unsigned char id3v2Header[10];
    size_t id3v2TagSize;

    if (gfc->VBR_seek_table.pos <= 0)
	return -1;


	/* Clear stream buffer */
	memset(pbtStreamBuffer,0x00,sizeof(pbtStreamBuffer));

	/* Seek to end of file*/
	fseek(fpStream,0,SEEK_END);

	/* Get file size */
	lFileSize=ftell(fpStream);

	/* Abort if file has zero length. Yes, it can happen :) */
	if (lFileSize==0)
		return -1;

        /*
         * The VBR tag may NOT be located at the beginning of the stream.
         * If an ID3 version 2 tag was added, then it must be skipped to write
         * the VBR tag data.
         */

        /* seek to the beginning of the stream */
	fseek(fpStream,0,SEEK_SET);
        /* read 10 bytes in case there's an ID3 version 2 header here */
        fread(id3v2Header,1,sizeof id3v2Header,fpStream);
        /* does the stream begin with the ID3 version 2 file identifier? */
        if (!strncmp((char *)id3v2Header,"ID3",3)) {
          /* the tag size (minus the 10-byte header) is encoded into four
           * bytes where the most significant bit is clear in each byte */
          id3v2TagSize=(((id3v2Header[6] & 0x7f)<<21)
            | ((id3v2Header[7] & 0x7f)<<14)
            | ((id3v2Header[8] & 0x7f)<<7)
            | (id3v2Header[9] & 0x7f))
            + sizeof id3v2Header;
        } else {
          /* no ID3 version 2 tag in this stream */
          id3v2TagSize=0;
        }

	/* Seek to first real frame */
	fseek(fpStream,id3v2TagSize+gfp->TotalFrameSize,SEEK_SET);

	/* Read the header (first valid frame) */
	fread(pbtStreamBuffer,4,1,fpStream);

	/* the default VBR header. 48 kbps layer III, no padding, no crc */
	/* but sampling freq, mode andy copyright/copy protection taken */
	/* from first valid frame */
	pbtStreamBuffer[0]=(uint8_t) 0xff;
	abyte = (pbtStreamBuffer[1] & (char) 0xf1);
	{	
		int bitrate;
		if (1==gfp->version) {
		  bitrate = XING_BITRATE1;
		} else {
		  if (gfp->out_samplerate < 16000 )
			bitrate = XING_BITRATE25;
		  else
			bitrate = XING_BITRATE2;
		}
		
		if (gfp->VBR==vbr_off)
			bitrate = gfp->brate;

		bbyte = 16*BitrateIndex(bitrate,gfp->version,gfp->out_samplerate);
	}

	/* Use as much of the info from the real frames in the
	 * Xing header:  samplerate, channels, crc, etc...
	 */ 
	if (gfp->version==1) {
	  /* MPEG1 */
	  pbtStreamBuffer[1]=abyte | (char) 0x0a;     /* was 0x0b; */
	  abyte = pbtStreamBuffer[2] & (char) 0x0d;   /* AF keep also private bit */
	  pbtStreamBuffer[2]=(char) bbyte | abyte;     /* 64kbs MPEG1 frame */
	}else{
	  /* MPEG2 */
	  pbtStreamBuffer[1]=abyte | (char) 0x02;     /* was 0x03; */
	  abyte = pbtStreamBuffer[2] & (char) 0x0d;   /* AF keep also private bit */
	  pbtStreamBuffer[2]=(char) bbyte | abyte;     /* 64kbs MPEG2 frame */
	}

	/* Clear all TOC entries */
	memset(btToc,0,sizeof(btToc));

        Xing_seek_table (&gfc->VBR_seek_table, btToc);
        /* print_seeking (btToc); */

	/* Start writing the tag after the zero frame */
	nStreamIndex=gfc->sideinfo_len;
	/* note! Xing header specifies that Xing data goes in the
	 * ancillary data with NO ERROR PROTECTION.  If error protecton
	 * in enabled, the Xing data still starts at the same offset,
	 * and now it is in sideinfo data block, and thus will not
	 * decode correctly by non-Xing tag aware players */
	if (gfp->error_protection) nStreamIndex -= 2;

	/* Put Vbr tag */
	if (gfp->VBR == vbr_off)
	{
		pbtStreamBuffer[nStreamIndex++]=VBRTag2[0];
		pbtStreamBuffer[nStreamIndex++]=VBRTag2[1];
		pbtStreamBuffer[nStreamIndex++]=VBRTag2[2];
		pbtStreamBuffer[nStreamIndex++]=VBRTag2[3];

	}
	else
	{
		pbtStreamBuffer[nStreamIndex++]=VBRTag[0];
		pbtStreamBuffer[nStreamIndex++]=VBRTag[1];
		pbtStreamBuffer[nStreamIndex++]=VBRTag[2];
		pbtStreamBuffer[nStreamIndex++]=VBRTag[3];
	}	

	/* Put header flags */
	CreateI4(&pbtStreamBuffer[nStreamIndex],FRAMES_FLAG+BYTES_FLAG+TOC_FLAG+VBR_SCALE_FLAG);
	nStreamIndex+=4;

	/* Put Total Number of frames */
	CreateI4(&pbtStreamBuffer[nStreamIndex],gfp->nVbrNumFrames);
	nStreamIndex+=4;

	/* Put Total file size */
	CreateI4(&pbtStreamBuffer[nStreamIndex],(int)lFileSize);
	nStreamIndex+=4;

	/* Put TOC */
	memcpy(&pbtStreamBuffer[nStreamIndex],btToc,sizeof(btToc));
	nStreamIndex+=sizeof(btToc);


	if (gfp->error_protection) {
	  /* (jo) error_protection: add crc16 information to header */
	  CRC_writeheader(gfc, pbtStreamBuffer);
	}



	//work out CRC so far: initially crc = 0
	for (i = 0;i< nStreamIndex ;i++)
		crc = CRC_update_lookup(pbtStreamBuffer[i], crc);

	/*Put LAME VBR info*/
	nStreamIndex+=PutLameVBR(gfp, fpStream, pbtStreamBuffer + nStreamIndex, id3v2TagSize,crc);

#ifdef DEBUG_VBRTAG
	{
	  VBRTAGDATA TestHeader;
	  GetVbrTag(&TestHeader,pbtStreamBuffer);
	}
#endif

	/*Seek to the beginning of the stream */
	fseek(fpStream,id3v2TagSize,SEEK_SET);

        /* Put it all to disk again */
	if (fwrite(pbtStreamBuffer,(unsigned int)gfp->TotalFrameSize,1,fpStream)!=1)
	{
		return -1;
	}
	/* Save to delete the frame buffer */
	//free(gfp->pVbrFrames);  see HACKING for instructions on how
	//gfp->pVbrFrames=NULL;   memory in 'gfp' is allocated/free'd

	return 0;       /* success */
}

